package org.zachary.imitation.raft;

import org.zachary.imitation.raft.core.DefaultJRaftServiceFactory;
import org.zachary.imitation.raft.option.NodeOptions;
import org.zachary.utils.io.FileUtils;

import java.io.File;
import java.io.IOException;
import java.time.Duration;

/**
 * Created by guozhaokun on 2024/8/30 22:42.
 */
public class Bootstrap {

	/** 集群服务类，是用来启动集群的 */
	private RaftGroupService raftGroupService;

	public Bootstrap(final String dataPath, final String groupId, final PeerId serverId, final NodeOptions nodeOptions) throws IOException {
		// 根据用户配置的本地路径创建文件夹
		FileUtils.forceMkdir(new File(dataPath));

		//创建RPC服务器，这个服务器的具体实现先不必关心，后两章就会讲到
		final RpcServer rpcServer = null; // RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint());

		// 设置日志存储文件的本地路径，File.separator就是路径分隔符
		nodeOptions.setLogUri(dataPath + File.separator + "log");
		// 设置元数据存储文件的本地路径
		nodeOptions.setRaftMetaUri(dataPath + File.separator + "raft_meta");

		// 设置组件的服务工厂
		nodeOptions.setServiceFactory(new DefaultJRaftServiceFactory());

		// 创建集群服务对象并赋值
		this.raftGroupService = new RaftGroupService(groupId, serverId, nodeOptions, rpcServer);
	}

	/**
	 * 在 sofajraft 中是按照依次的 4 个参数（{dataPath} {groupId} {serverId} {initConf}）来给节点传输配置信息（{@link NodeOptions}）
	 * ● dataPath：要持久化的数据的持久化路径
	 * ● groupId：节点所属的组，即集群 ID
	 * ● serverId：当前节点的 ip:port
	 * ● initConf：集群中所有节点的 ip:port，使用逗号分割
	 */
	public static void main(String[] args) throws InterruptedException, IOException {
		if (args.length != 4) {
			System.out.println("Usage : java com.alipay.sofa.jraft.example.counter.CounterServer {dataPath} {groupId} {serverId} {initConf}");
			System.out.println("Example: java com.alipay.sofa.jraft.example.counter.CounterServer /tmp/server1 counter 127.0.0.1:8081 127.0.0.1:8081,127.0.0.1:8082,127.0.0.1:8083");
			System.exit(1);
		}

		final String dataPath = args[0];
		final String groupId = args[1];
		final String serverIdStr = args[2];
		final String initConfStr = args[3];

		// 使用 NodeOptions 对象来封装当前节点的配置参数
		final NodeOptions nodeOptions = new NodeOptions();
		// 设置超时选举时间，超过这个时间没有接收到 Leader 发送的心跳信息就会触发新一轮选举
		nodeOptions.setElectionTimeout(Duration.ofSeconds(1));

		// 创建 PeerId 对象用来封装代表当前服务器节点的信息的
		final PeerId serverId = new PeerId();
		// 把当前节点的 ip:port 字符串解析到 PeerId 对象中
		if (!serverId.parse(serverIdStr)) {
			throw new IllegalArgumentException("Fail to parse serverId:" + serverIdStr);
		}

		// 创建集群配置对象，这个对象中封装着整个集群中的节点信息
		final Configuration initConf = new Configuration();
		// 把上面得到的字符串信息解析到该对象中，这时候配置类对象就拥有了集群中所有节点的信息
		if (!initConf.parse(initConfStr)) {
			throw new IllegalArgumentException("Fail to parse initConf:" + initConfStr);
		}
		nodeOptions.setInitialConf(initConf);

		// 启动当前节点以及集群服务
		final Bootstrap counterServer = new Bootstrap(dataPath, groupId, serverId, nodeOptions);
		// 通过集群服务类对象启动集群
		counterServer.raftGroupService.start();
	}
}
